package top.goldenyear.porpoise.dev.code.service;

import static top.goldenyear.porpoise.common.core.exception.util.ServiceExceptionUtils.exception;
import static top.goldenyear.porpoise.common.core.util.collection.CollUtils.convertSet;
import static top.goldenyear.porpoise.dev.code.enums.CodegenErrorCode.*;
import static top.goldenyear.porpoise.framework.web.core.util.WebUtils.renderFile;

import cn.hutool.v7.core.collection.CollUtil;
import cn.hutool.v7.core.collection.ListUtil;
import cn.hutool.v7.core.compress.ZipUtil;
import cn.hutool.v7.core.io.IoUtil;
import cn.hutool.v7.core.text.StrUtil;
import com.easy.query.api.proxy.client.EasyEntityQuery;
import com.easy.query.solon.annotation.Db;
import com.jfinal.kit.Kv;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.*;
import java.util.function.BiPredicate;
import java.util.stream.Collectors;
import javax.sql.DataSource;
import lombok.extern.slf4j.Slf4j;
import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Inject;
import org.noear.solon.core.handle.Context;
import org.noear.solon.data.annotation.Transaction;
import top.goldenyear.porpoise.common.core.exception.ErrorCode;
import top.goldenyear.porpoise.common.core.exception.ServiceException;
import top.goldenyear.porpoise.common.core.model.PlainOption;
import top.goldenyear.porpoise.common.core.util.collection.CollUtils;
import top.goldenyear.porpoise.dev.code.config.CodeConfig;
import top.goldenyear.porpoise.dev.code.dto.req.CodeGenCreateReq;
import top.goldenyear.porpoise.dev.code.dto.req.CodeGenUpdateReq;
import top.goldenyear.porpoise.dev.code.dto.req.DbTableSearchReq;
import top.goldenyear.porpoise.dev.code.dto.res.CodeGenPreviewRes;
import top.goldenyear.porpoise.dev.code.dto.res.CodeGenRes;
import top.goldenyear.porpoise.dev.code.dto.res.DbTableRes;
import top.goldenyear.porpoise.dev.code.model.CodeColumn;
import top.goldenyear.porpoise.dev.code.model.CodeGen;
import top.goldenyear.porpoise.dev.code.model.CodeTable;
import top.goldenyear.porpoise.dev.code.model.proxy.CodeTableProxy;
import top.goldenyear.porpoise.dev.code.util.CodeGenMetaBuilder;
import top.goldenyear.porpoise.dev.code.util.CodeGenUtils;
import top.goldenyear.porpoise.framework.common.model.LoginUser;
import top.goldenyear.porpoise.framework.common.util.object.BeanUtils;
import top.goldenyear.porpoise.framework.orm.core.model.IModel;
import top.goldenyear.porpoise.framework.web.core.annotation.Deform;
import top.goldenyear.porpoise.framework.web.core.generator.DeCoupledGenerator;
import top.goldenyear.porpoise.framework.web.core.generator.config.GlobalConfig;
import top.goldenyear.porpoise.framework.web.core.generator.enums.TemplateType;
import top.goldenyear.porpoise.framework.web.core.generator.model.ColumnMeta;
import top.goldenyear.porpoise.framework.web.core.generator.model.TableMeta;
import top.goldenyear.porpoise.framework.web.core.model.PageResult;
import top.goldenyear.porpoise.framework.web.core.model.query.QPage;
import top.goldenyear.porpoise.framework.web.core.service.BaseService;

/**
 * 表配置 Service 服务层
 *
 * @author airhead
 * @since 2025-02-04
 */
@Component
@Slf4j
public class CodeTableService extends BaseService<CodeTable, CodeTableProxy> {
  @Db private EasyEntityQuery easyEntityQuery;
  @Inject private CodeColumnService codeColumnService;
  @Inject private CodeDbService codeDbService;
  @Inject private CodeConfig codegenProps;
  @Inject private CodeGenService codeGenService;

  public CodeTableService() {
    super(CodeTable.class, CodeTableProxy.class);
  }

  public Boolean batchDelCodeTable(List<Long> ids) {
    for (Long id : ids) {
      if (getById(id) == null) {
        throw new ServiceException(ErrorCode.builder().msg("记录不存在").build());
      }
    }

    return deleteByIds(ids);
  }

  public List<CodeTable> listCodeTable(CodeTable codeTable) {
    return list(codeTable);
  }

  @Deform(keep = {"id", "name"})
  public List<CodeTable> simpleListCodeTable(CodeTable codeTable) {
    return list(codeTable);
  }

  public PageResult<CodeTable> paginateCodeTable(QPage qPage, CodeTable codeTable) {
    return paginate(qPage, codeTable);
  }

  public List<PlainOption> optionsCodeTable(CodeTable codeTable) {
    return listAs(codeTable, PlainOption.class);
  }

  public List<CodeTable> listByDbId(Integer dbId) {
    return entityQueryable().where(c -> c.dbId().eq(dbId)).toList();
  }

  public List<CodeTable> listByTemplateTypeAndMasterTableId(
      Integer templateType, Integer masterTableId) {
    return entityQueryable()
        .where(
            c -> {
              c.templateType().eq(templateType);
              c.masterTableId().eq(masterTableId);
            })
        .toList();
  }

  public CodeTable getByNameAndDbId(String tableName, Integer dbId) {
    return entityQueryable()
        .where(
            c -> {
              c.name().eq(tableName);
              c.dbId().eq(dbId);
            })
        .firstOrNull();
  }

  public CodeTable getByName(String name) {
    return entityQueryable()
        .where(
            c -> {
              c.name().eq(name);
            })
        .firstOrNull();
  }

  public PageResult<DbTableRes> paginateDbTable(DbTableSearchReq searchReq, QPage qPage) {
    if (Objects.isNull(searchReq.getDbId())) {
      return PageResult.of(qPage);
    }

    List<TableMeta> tableMetas =
        codeDbService.getTableList(
            searchReq.getDbId(), searchReq.getName(), searchReq.getComment());
    // 移除在 Codegen 中，已经存在的
    Set<String> existsTables = convertSet(listByDbId(searchReq.getDbId()), CodeTable::getName);
    tableMetas.removeIf(table -> existsTables.contains(table.getName()));
    if (CollUtil.isEmpty(tableMetas)) {
      return PageResult.of(qPage);
    }

    List<List<TableMeta>> fullList = ListUtil.partition(tableMetas, qPage.getPageSize());
    List<TableMeta> tableMeteList = fullList.get(qPage.getPageNumber() - 1);

    List<DbTableRes> list =
        BeanUtils.toBean(
            tableMeteList,
            DbTableRes.class,
            (i) -> {
              i.setId(i.getName());
              i.setDbId(searchReq.getDbId());
            });

    PageResult<DbTableRes> pageResult = PageResult.of(qPage);
    pageResult.setTotal(tableMetas.size());
    return pageResult.setRecords(list);
  }

  @Transaction
  public List<Integer> createCodeTable(CodeGenCreateReq createReq, LoginUser loginUser) {
    List<Integer> ids = new ArrayList<>(createReq.getTableNames().size());

    // 遍历添加。虽然效率会低一点，但是没必要做成完全批量，因为不会这么大量
    for (String tableName : createReq.getTableNames()) {
      Integer codeTableId = createCodeTable(createReq.getDbId(), tableName, loginUser);
      ids.add(codeTableId);
    }

    return ids;
  }

  public CodeGenRes getCodeTable(Integer id, LoginUser loginUser) {
    CodeTable table = getById(id);
    fillCodeTable(table, loginUser);

    List<CodeColumn> columns = codeColumnService.listByTableId(id);
    // 拼装返回
    CodeGenRes codeGenRes = new CodeGenRes();
    codeGenRes.setTable(table);
    codeGenRes.setColumns(columns);
    return codeGenRes;
  }

  @Transaction
  public void updateCodeTable(CodeGenUpdateReq updateReq, LoginUser loginUser) {
    // 更新个人全集配置
    codeGenService.upsertCodeGen(updateReq, loginUser);

    // 校验是否已经存在
    if (getById(updateReq.getTable().getId()) == null) {
      throw exception(CODEGEN_TABLE_NOT_EXISTS);
    }

    // 校验主表字段存在
    if (Objects.equals(updateReq.getTable().getTemplateType(), TemplateType.SUB.getType())) {
      if (getById(updateReq.getTable().getMasterTableId()) == null) {
        throw exception(CODEGEN_MASTER_TABLE_NOT_EXISTS, updateReq.getTable().getMasterTableId());
      }
      // 关联主表的字段不存在
      if (CollUtil.getFirst(
              updateReq.getColumns(),
              column -> column.getId().equals(updateReq.getTable().getJoinColumnId()))
          == null) {
        throw exception(CODEGEN_SUB_COLUMN_NOT_EXISTS, updateReq.getTable().getJoinColumnId());
      }
    }

    // 更新 table 表定义
    updateById(updateReq.getTable());
    // 更新 column 字段定义
    List<CodeColumn> updateColumnObjs = updateReq.getColumns();
    updateColumnObjs.forEach(
        updateColumnObj -> codeColumnService.updateById(updateColumnObj, true));
  }

  @Transaction
  public void deleteCodeTable(Integer id) {
    // 校验是否已经存在
    if (getById(id) == null) {
      throw exception(CODEGEN_TABLE_NOT_EXISTS);
    }

    // 删除 table 表定义
    deleteById(id);
    // 删除 column 字段定义
    codeColumnService.deleteByTableId(id);
  }

  @Transaction
  public void syncCodeTable(Integer tableId) {
    // 校验是否已经存在
    CodeTable table = getById(tableId);
    if (table == null) {
      throw exception(CODEGEN_TABLE_NOT_EXISTS);
    }

    // 从数据库中，获得数据库表结构
    TableMeta tableMeta = codeDbService.getTable(table.getDbId(), table.getName());
    // 执行同步
    syncCodeTable(tableId, tableMeta);
  }

  public List<CodeGenPreviewRes> previewCodeTable(Integer id) {
    Map<String, String> codes = generationCode(id);
    return CollUtils.convertList(
        codes.entrySet(),
        entry -> new CodeGenPreviewRes().setFilePath(entry.getKey()).setCode(entry.getValue()));
  }

  public void generateCode(Integer id, Context context) throws IOException {
    // 生成代码
    Map<String, String> codes = generationCode(id);
    // 构建 zip 包
    String[] paths = codes.keySet().toArray(new String[0]);
    ByteArrayInputStream[] ins =
        codes.values().stream().map(IoUtil::toUtf8Stream).toArray(ByteArrayInputStream[]::new);
    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    ZipUtil.zip(outputStream, paths, ins);
    // 输出
    renderFile(context, "codegen.zip", outputStream.toByteArray());
  }

  public Boolean generateFile(Integer id) {
    // 校验是否已经存在
    CodeTable codeTable = getById(id);
    if (codeTable == null) {
      throw exception(CODEGEN_TABLE_NOT_EXISTS);
    }

    if (StrUtil.isBlank(codeTable.getSourceDir())) {
      throw exception(CODEGEN_TABLE_SOURCE_DIR_IS_EMPTY);
    }

    generationFile(id);

    return true;
  }

  @Override
  protected EasyEntityQuery easyEntityQuery() {
    return easyEntityQuery;
  }

  private void fillCodeTable(CodeTable table, LoginUser loginUser) {
    CodeGen codeGen = codeGenService.getByAccountId(loginUser.getId());
    if (codeGen == null) {
      return;
    }

    if (StrUtil.isBlank(table.getSourceDir())) {
      table.setSourceDir(codeGen.getSourceDir());
    }

    if (StrUtil.isBlank(table.getUiSourceDir())) {
      table.setUiSourceDir(codeGen.getUiSourceDir());
    }

    if (StrUtil.isBlank(table.getBasePackage())) {
      table.setBasePackage(codeGen.getBasePackage());
    }

    if (StrUtil.isBlank(table.getAuthor())) {
      table.setAuthor(codeGen.getAuthor());
    }
  }

  private Map<String, String> generationCode(Integer tableId) {
    List<Kv> list = generateCode(tableId, new DeCoupledGenerator());
    return list.stream().collect(Collectors.toMap(k -> k.getStr("file"), v -> v.getStr("content")));
  }

  private void generationFile(Integer tableId) {
    DeCoupledGenerator generator = new DeCoupledGenerator();
    List<Kv> codeList = generateCode(tableId, generator);
    generator.generateFile(codeList);
  }

  private List<Kv> generateCode(Integer tableId, DeCoupledGenerator generator) {
    // 校验是否已经存在
    CodeTable codeTable = getById(tableId);
    if (codeTable == null) {
      throw exception(CODEGEN_TABLE_NOT_EXISTS);
    }

    List<CodeColumn> codeColumns = codeColumnService.listByTableId(tableId);
    if (CollUtil.isEmpty(codeColumns)) {
      throw exception(CODEGEN_COLUMN_NOT_EXISTS);
    }

    // 如果是主子表，则加载对应的子表信息
    List<CodeTable> subTables = new ArrayList<>();
    Map<Integer, List<CodeColumn>> subColumnsMap = new HashMap<>(0);
    if (TemplateType.isMaster(codeTable.getTemplateType())) {
      // 校验子表存在
      subTables = listByTemplateTypeAndMasterTableId(TemplateType.SUB.getType(), codeTable.getId());
      if (CollUtil.isEmpty(subTables)) {
        throw exception(CODEGEN_MASTER_GENERATION_FAIL_NO_SUB_TABLE);
      }

      // 校验子表的关联字段存在
      for (CodeTable subTable : subTables) {
        List<CodeColumn> subColumns = codeColumnService.listByTableId(subTable.getId());
        CodeColumn codeColumn =
            CollUtil.getFirst(
                subColumns, column -> column.getId().equals(subTable.getJoinColumnId()));
        if (codeColumn == null) {
          throw exception(CODEGEN_SUB_COLUMN_NOT_EXISTS, subTable.getId());
        }

        subColumnsMap.put(subTable.getId(), subColumns);
      }
    }

    // 执行生成
    GlobalConfig globalConfig = generator.getGlobalConfig();
    globalConfig.setSourceDir(codeTable.getSourceDir());
    globalConfig.setUiSourceDir(codeTable.getUiSourceDir());
    globalConfig.setOverwrite(codeTable.getEnableOverwrite());
    globalConfig.getModuleConfigs().get("model.base").setSuperInterface(IModel.class);

    DataSource dataSource = codeDbService.getDataSourceById(codeTable.getDbId());
    generator.setDataSource(dataSource);
    CodeGenMetaBuilder codeGenMetaBuilder =
        new CodeGenMetaBuilder(globalConfig, codeTable, codeColumns, subTables, subColumnsMap);
    generator.setMetaBuilder(codeGenMetaBuilder);
    return generator.generateCode();
  }

  private Integer createCodeTable(Integer dbId, String tableName, LoginUser loginUser) {
    // 从数据库中，获得数据库表结构
    TableMeta tableMeta = codeDbService.getTable(dbId, tableName);

    // 先插入个人的全局配置
    CodeGen codeGen = codeGenService.upsertCodeGen(null, loginUser);

    // 校验导入的表和字段非空
    validateTableMeta(tableMeta);

    // 校验是否已经存在
    if (getByNameAndDbId(tableMeta.getName(), dbId) != null) {
      throw exception(CODEGEN_TABLE_EXISTS);
    }

    // 构建 CodegenTableDO 对象，插入到 DB 中
    CodeTable codeTable = CodeGenUtils.buildCodeTable(dbId, codeGen, tableMeta);
    save(codeTable);

    // 构建 CodegenColumnDO 数组，插入到 DB 中
    List<CodeColumn> columns = CodeGenUtils.buildColumns(codeTable.getId(), tableMeta);

    codeColumnService.batchSave(columns);
    return codeTable.getId();
  }

  private void syncCodeTable(Integer tableId, TableMeta tableMeta) {
    // 1. 校验导入的表和字段非空
    validateTableMeta(tableMeta);
    List<ColumnMeta> tableFields = tableMeta.getColumns();

    // 2. 构建 CodegenColumn 数组，只同步新增的字段
    List<CodeColumn> codegenColumns = codeColumnService.listByTableId(tableId);
    Set<String> codegenColumnNames = convertSet(codegenColumns, CodeColumn::getName);

    // 3.1 计算需要【修改】的字段，插入时重新插入，删除时将原来的删除
    Map<String, CodeColumn> codegenColumnMap =
        CollUtils.convertMap(codegenColumns, CodeColumn::getName);
    BiPredicate<ColumnMeta, CodeColumn> primaryKeyPredicate =
        (tableField, codegenColumn) ->
            tableField.getType().equals(codegenColumn.getType())
                && Objects.equals(tableField.getNullable(), codegenColumn.getNullable())
                && Objects.equals(tableField.getPrimaryKey(), codegenColumn.getPrimaryKey())
                && tableField.getComment().equals(codegenColumn.getComment());
    Set<String> modifyFieldNames =
        tableFields.stream()
            .filter(
                tableField ->
                    codegenColumnMap.get(tableField.getName()) != null
                        && !primaryKeyPredicate.test(
                            tableField, codegenColumnMap.get(tableField.getName())))
            .map(ColumnMeta::getName)
            .collect(Collectors.toSet());
    // 3.2 计算需要【删除】的字段
    Set<String> tableFieldNames = convertSet(tableFields, ColumnMeta::getName);
    Set<Integer> deleteColumnIds =
        codegenColumns.stream()
            .filter(
                column ->
                    (!tableFieldNames.contains(column.getName()))
                        || modifyFieldNames.contains(column.getName()))
            .map(CodeColumn::getId)
            .collect(Collectors.toSet());
    // 移除已经存在的字段
    tableFields.removeIf(
        column ->
            codegenColumnNames.contains(column.getName())
                && (!modifyFieldNames.contains(column.getName())));
    if (CollUtil.isEmpty(tableFields) && CollUtil.isEmpty(deleteColumnIds)) {
      throw exception(CODEGEN_SYNC_NONE_CHANGE);
    }

    // 4.1 插入新增的字段
    List<CodeColumn> columns = CodeGenUtils.buildColumns(tableId, tableMeta);
    codeColumnService.batchSave(columns);

    // 4.2 删除不存在的字段
    if (CollUtil.isNotEmpty(deleteColumnIds)) {
      codeColumnService.deleteByIds(deleteColumnIds);
    }
  }

  void validateTableMeta(TableMeta tableMeta) {
    if (tableMeta == null) {
      throw exception(CODEGEN_IMPORT_TABLE_NULL);
    }

    if (StrUtil.isEmpty(tableMeta.getComment())) {
      throw exception(CODEGEN_TABLE_INFO_TABLE_COMMENT_IS_NULL);
    }

    if (CollUtil.isEmpty(tableMeta.getColumns())) {
      throw exception(CODEGEN_IMPORT_COLUMNS_NULL);
    }

    if (tableMeta.hasNotPrimaryKey()) {
      throw exception(CODEGEN_IMPORT_COLUMNS_NULL);
    }

    tableMeta
        .getColumns()
        .forEach(
            field -> {
              if (StrUtil.isEmpty(field.getComment())) {
                throw exception(CODEGEN_TABLE_INFO_COLUMN_COMMENT_IS_NULL, field.getName());
              }
            });
  }
}
